/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.model.objects;

import android.graphics.Paint;
import android.util.Log;

import com.hercules.android.receiver.painting.PaintersUtil;
import com.hercules.android.receiver.painting.views.AndroidXREView;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * <p>
 * This class represents an object that lives on the display hierarchy. The XRE display hierarchy is comprised of
 * XREView's that are described by the XREView object. Each XREView object has exactly one parent and zero or more
 * children. It is a simple JavaBean (POJO) that provides getters and setters for all private attributes and performs no
 * argument validation in the setters.
 * </p>
 * <p>
 * <strong>Thread safety:</strong> This class is mutable and not thread safe.
 * </p>
 *
 * @author saarixx, jmn
 * @author 7realm
 * @version 1.0
 */
public class XREView extends XREObject {

    /**
     * <p>
     * Represents default dimension value used both for width and height.
     * </p>
     */
    private static final int DEFAULT_DIMENSION = 300;

    /**
     * <p>
     * The parent view.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private XREView parent;

    /**
     * <p>
     * The value between 0 and 1 that dictates the view's opacity level.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private double alpha = 1.0;

    /**
     * <p>
     * The application-defined string that can be used to identify the view on the hierarchy.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private String name = "";

    /**
     * <p>
     * The array of two elements that specifies the view's width and height, in pixels.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     * <p>
     * AndroidAssembly1 changed to width, height order
     */
    private int[] dimensions = {DEFAULT_DIMENSION, DEFAULT_DIMENSION};

    /**
     * <p>
     * The view's transformation matrix. Dictates the view's position, rotation and scale values. Defaults to identity
     * matrix.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private double[][] matrix = {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}};
    
    /**
     * <p>
     * The resource associated with this view.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private XREResource resource;

    /**
     * <p>
     * The XREResourceOptions instance containing the desired resource options to use for the association between this
     * view and its resource.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private XREResourceOptions resourceOptions;

    /**
     * <p>
     * The mask image associated with this view.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private XREImage mask;

    /**
     * <p>
     * The value indicating whether the receiver should ignore mouse events that are generated by this view.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private boolean ignoreMouse;

    /**
     * <p>
     * The value indicating whether the view is visible. Defaults to true.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private boolean visible = true;

    /**
     * <p>
     * The value indicating whether modifications to this view, its children or any resources applied to them should be
     * reflected on screen. When this value is set to false, the view should not change on screen until it is set to
     * true. Defaults to true.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private boolean painting = true;

    /**
     * <p>
     * The value indicating whether the view will clip the child views whose dimensions exceed the dimensions of this
     * view.
     * </p>
     * <p>
     * Can be any value.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private boolean clip;

    /**
     * Represents whether the paint property should be disabled.
     */
    private boolean paintPropertyDisabled;
    
    /**
     * <p>
     * The children of this XREView.
     * </p>
     * <p>
     * Can be any value, can contain any values.
     * </p>
     * <p>
     * Has getter and setter.
     * </p>
     */
    private List<XREView> children = new ArrayList<XREView>();

    /**
     * Represents absolute scale value for x axis.
     * <p>
     * Can be any value.
     * <p>
     * Has getter and setter.
     * <p>
     * AndroidAssembly2 new field with getter and setter
     */
    private float absoluteScaleX = 1;

    /**
     * Represents absolute scale value for y axis. *
     * <p>
     * Can be any value.
     * <p>
     * Has getter and setter.
     * <p>
     * AndroidAssembly2 new field with getter and setter
     */
    private float absoluteScaleY = 1;

    /**
     * Represents internal translate by X axis that can occur at translation animation. *
     * <p>
     * Can be any value.
     * <p>
     * Has getter and setter.
     * <p>
     * AndroidAssembly2 new field with getter and setter
     */
    private float internalTranslationX;

    /**
     * Represents internal translate by X axis that can occur at translation animation. *
     * <p>
     * Can be any value.
     * <p>
     * Has getter and setter.
     * <p>
     * AndroidAssembly2 new field with getter and setter
     */
    private float internalTranslationY;

    /**
     * Represents whether an animation is being played on this view.
     */
    private boolean playingAnimation = false;
    
    /**
     * Represents the corresponding view.
     */
    private AndroidXREView androidXREView;
    
    /**
     * <p>
     * Creates new instance of {@link XREView} class.
     * </p>
     */
    public XREView() {
        // empty constructor
    }

    /**
     * <p>
     * Retrieves the parent view.
     * </p>
     *
     * @return the parent view
     */
    public XREView getParent() {
        return parent;
    }

    /**
     * <p>
     * Sets the parent view.
     * </p>
     *
     * @param parent the parent view
     */
    public void setParent(XREView parent) {
        this.parent = parent;
    }

    /**
     * <p>
     * Retrieves the value between 0 and 1 that dictates the view's opacity level.
     * </p>
     *
     * @return the value between 0 and 1 that dictates the view's opacity level
     */
    public double getAlpha() {
        return alpha;
    }

    /**
     * <p>
     * Sets the value between 0 and 1 that dictates the view's opacity level.
     * </p>
     *
     * @param alpha the value between 0 and 1 that dictates the view's opacity level
     */
    public void setAlpha(double alpha) {
        this.alpha = alpha;
    }

    /**
     * <p>
     * Retrieves the application-defined string that can be used to identify the view on the hierarchy.
     * </p>
     *
     * @return the application-defined string that can be used to identify the view on the hierarchy
     */
    public String getName() {
        return name;
    }

    /**
     * <p>
     * Sets the application-defined string that can be used to identify the view on the hierarchy.
     * </p>
     *
     * @param name the application-defined string that can be used to identify the view on the hierarchy
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * <p>
     * Retrieves the array of two elements that specifies the view's height and width, in pixels.
     * </p>
     *
     * @return the array of two elements that specifies the view's height and width, in pixels
     */
    public int[] getDimensions() {
        return dimensions;
    }

    /**
     * <p>
     * Sets the array of two elements that specifies the view's height and width, in pixels.
     * </p>
     *
     * @param dimensions the array of two elements that specifies the view's height and width, in pixels
     */
    public void setDimensions(int[] dimensions) {
        this.dimensions = dimensions;
    }

    /**
     * <p>
     * Retrieves the view's transformation matrix.
     * </p>
     *
     * @return the view's transformation matrix
     */
    public double[][] getMatrix() {
        return matrix;
    }

    /**
     * <p>
     * Sets the view's transformation matrix.
     * </p>
     *
     * @param matrix the view's transformation matrix
     */
    public void setMatrix(double[][] matrix) {
        this.matrix = matrix;
    }

    /**
     * <p>
     * Retrieves the resource associated with this view.
     * </p>
     *
     * @return the resource associated with this view
     */
    public XREResource getResource() {
        return resource;
    }

    /**
     * <p>
     * Sets the resource associated with this view.
     * </p>
     *
     * @param resource the resource associated with this view
     */
    public void setResource(XREResource resource) {
        this.resource = resource;
    }

    /**
     * <p>
     * Retrieves the XREResourceOptions instance containing the desired resource options to use for the association
     * between this view and its resource.
     * </p>
     *
     * @return the XREResourceOptions instance containing the desired resource options to use for the association
     * between this view and its resource
     */
    public XREResourceOptions getResourceOptions() {
        return resourceOptions;
    }

    /**
     * <p>
     * Sets the XREResourceOptions instance containing the desired resource options to use for the association between
     * this view and its resource.
     * </p>
     *
     * @param resourceOptions the XREResourceOptions instance containing the desired resource options to use for the
     * association between this view and its resource
     */
    public void setResourceOptions(XREResourceOptions resourceOptions) {
        this.resourceOptions = resourceOptions;
    }

    /**
     * <p>
     * Retrieves the mask associated with this view.
     * </p>
     *
     * @return the mask associated with this view
     */
    public XREImage getMask() {
        return mask;
    }

    /**
     * <p>
     * Sets the mask associated with this view.
     * </p>
     *
     * @param mask the mask associated with this view
     */
    public void setMask(XREImage mask) {
        this.mask = mask;
    }

    /**
     * <p>
     * Retrieves the value indicating whether the receiver should ignore mouse events that are generated by this view.
     * </p>
     *
     * @return the value indicating whether the receiver should ignore mouse events that are generated by this view
     */
    public boolean isIgnoreMouse() {
        return ignoreMouse;
    }

    /**
     * <p>
     * Sets the value indicating whether the receiver should ignore mouse events that are generated by this view.
     * </p>
     *
     * @param ignoreMouse the value indicating whether the receiver should ignore mouse events that are generated by
     * this view
     */
    public void setIgnoreMouse(boolean ignoreMouse) {
        this.ignoreMouse = ignoreMouse;
    }

    /**
     * <p>
     * Retrieves the value indicating whether the view is visible.
     * </p>
     *
     * @return the value indicating whether the view is visible
     */
    public boolean isVisible() {
        return visible;
    }

    /**
     * <p>
     * Sets the value indicating whether the view is visible.
     * </p>
     *
     * @param visible the value indicating whether the view is visible
     */
    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    /**
     * <p>
     * Retrieves the value indicating whether modifications to this view, its children or any resources applied to them
     * should be reflected on screen.
     * </p>
     *
     * @return the value indicating whether modifications to this view, its children or any resources applied to them
     * should be reflected on screen
     */
    public boolean isPainting() {
        return painting;
    }

    /**
     * <p>
     * Sets the value indicating whether modifications to this view, its children or any resources applied to them
     * should be reflected on screen.
     * </p>
     *
     * @param painting the value indicating whether modifications to this view, its children or any resources applied to
     * them should be reflected on screen
     */
    public void setPainting(boolean painting) {
        this.painting = painting;
    }

    /**
     * <p>
     * Retrieves the value indicating whether the view will clip the child views whose dimensions exceed the dimensions
     * of this view.
     * </p>
     *
     * @return the value indicating whether the view will clip the child views whose dimensions exceed the dimensions of
     * this view
     */
    public boolean isClip() {
        return clip;
    }

    /**
     * <p>
     * Sets the value indicating whether the view will clip the child views whose dimensions exceed the dimensions of
     * this view.
     * </p>
     *
     * @param clip the value indicating whether the view will clip the child views whose dimensions exceed the
     * dimensions of this view
     */
    public void setClip(boolean clip) {
        this.clip = clip;
    }

    /**
     * <p>
     * Retrieves the children of this XREView.
     * </p>
     *
     * @return the children of this XREView
     */
    public List<XREView> getChildren() {
        return children;
    }

    /**
     * <p>
     * Sets the children of this XREView.
     * </p>
     *
     * @param children the children of this XREView
     */
    public void setChildren(List<XREView> children) {
        this.children = children;
    }

    /**
     * <p>
     * Retrieves the height of the view.
     * </p>
     *
     * @return the height of the view
     * @throws IllegalStateException if dimensions is null or doesn't contain exactly 2 elements
     */
    public int getHeight() {
        assertDimensions();
        return dimensions[1];
    }

    /**
     * <p>
     * Retrieves the width of the view.
     * </p>
     *
     * @return the width of the view
     * @throws IllegalStateException if dimensions is null or doesn't contain exactly 2 elements
     */
    public int getWidth() {
        assertDimensions();
        return dimensions[0];
    }

    /**
     * Returns view x coordinate.
     *
     * @return the left coordinate
     */
    public int getViewX() {
        if (matrix == null || matrix.length != 4) {
            throw new IllegalStateException("Property matrix is null or doesn't have correct elements count.");
        }

        return (int) matrix[0][3];
    }

    /**
     * Returns view y coordinate.
     *
     * @return the top coordinate
     */
    public int getViewY() {
        if (matrix == null || matrix.length != 4) {
            throw new IllegalStateException("Property matrix is null or doesn't have correct elements count.");
        }

        return (int) matrix[1][3];
    }

    /**
     * Gets the absolute scale value for x axis.
     *
     * @return the absolute scale value for x axis
     */
    public float getAbsoluteScaleX() {
        return absoluteScaleX;
    }

    /**
     * Sets the absolute scale value for x axis.
     *
     * @param absoluteScaleX the absolute scale value for x axis
     */
    public void setAbsoluteScaleX(float absoluteScaleX) {
        this.absoluteScaleX = absoluteScaleX;
    }

    /**
     * Gets the absolute scale value for x axis.
     *
     * @return the absolute scale value for x axis
     */
    public float getAbsoluteScaleY() {
        return absoluteScaleY;
    }

    /**
     * Sets the absolute scale value for x axis.
     *
     * @param absoluteScaleY the absolute scale value for x axis
     */
    public void setAbsoluteScaleY(float absoluteScaleY) {
        this.absoluteScaleY = absoluteScaleY;
    }

    /**
     * Gets the internal translate by X axis that can occur at translation animation.
     *
     * @return the internal translate by X axis that can occur at translation animation
     */
    public float getInternalTranslationX() {
        return internalTranslationX;
    }

    /**
     * Sets the internal translate by X axis that can occur at translation animation.
     *
     * @param internalTranslationX the internal translate by X axis that can occur at translation animation
     */
    public void setInternalTranslationX(float internalTranslationX) {
        this.internalTranslationX = internalTranslationX;
    }

    /**
     * Gets the internal translate by Y axis that can occur at translation animation.
     *
     * @return the internal translate by Y axis that can occur at translation animation
     */
    public float getInternalTranslationY() {
        return internalTranslationY;
    }

    /**
     * Sets the internal translate by Y axis that can occur at translation animation.
     *
     * @param internalTranslationY the internal translate by Y axis that can occur at translation animation
     */
    public void setInternalTranslationY(float internalTranslationY) {
        this.internalTranslationY = internalTranslationY;
    }

    /**
     * Represents measured horizontal size of the resource.
     * <p>
     * AndroidAssembly3 this field and it's getter was added in Android Assembly 3.
     */
    private int measuredHorizontalExtent;

    /**
     * Represents measured vertical size of the resource.
     * <p>
     * AndroidAssembly3 this field and it's getter was added in Android Assembly 3.
     */
    private int measuredVerticalExtent;

    /**
     * Gets the measured horizontal size of the resource.
     *
     * @return the measured horizontal size of the resource
     */
    public int getMeasuredHorizontalExtent() {
        return measuredHorizontalExtent;
    }

    /**
     * Gets the measured vertical size of the resource.
     *
     * @return the measured vertical size of the resource
     */
    public int getMeasuredVerticalExtent() {
        return measuredVerticalExtent;
    }

    /**
     * Update measured sizes of the resource.
     * <p>
     * AndroidAssembly3 this method was added in Android Assembly 3.
     *
     * @param view the Android view.
     */
    public void updateMeasuredExtent(AndroidXREView view) {
        measuredHorizontalExtent = view.getResourceMeasuredWidth();
        measuredVerticalExtent = view.getResourceMeasuredHeight();
    }
    
    /**
     * Method that will return measured coordinates of char in {@link XREText} resource.
     * <p>
     * AndroidAssembly3 this method was added in Android Assembly 3.
     *
     * @param charToMeasure the index of char to measure
     * @return the position of measured char
     */
    public CharPosition textMeasuredChar(int charToMeasure) {
        if (resource instanceof XREText) {
            // get resource
            XREText xreText = (XREText) resource;
            String text = xreText.getText();

            // apply textStartPos and end position will be our required character
            if (resourceOptions != null) {
                text = text.substring(resourceOptions.getTextStartChar(), charToMeasure);
            }

            // get paint object
            Paint paint = PaintersUtil.createPaint(xreText);
            float textSize =
                -paint.getFontMetrics().top + Math.max(xreText.getLeading(), paint.getFontMetrics().bottom);

            // get view dimensions
            int width = getWidth();
            int height = getHeight();

            // first and last line's intent
            int startIntent = resourceOptions == null ? 0 : (int) resourceOptions.getTextStartPos(0);
            int endIntent = resourceOptions == null ? 0 : (int) resourceOptions.getTextStopPos(0);

            // split text to lines
            List<String> lines = new LinkedList<String>(Arrays.asList(text.split("\n")));

            // wrap lines if needed
            PaintersUtil.wrapLines(resourceOptions, lines, paint, width, startIntent, endIntent);

            // get text height
            float textHeight = lines.size() * textSize > height ? height : lines.size() * textSize;

            // align text vertically
            float top = 0;
            if (resourceOptions != null) {
                switch (resourceOptions.getVerticalAlign()) {
                case CENTER:
                    top = (height - textHeight) / 2;
                    break;
                case BOTTOM:
                    top = height - textHeight;
                default:
                    break;
                }
            }

            // add vertical intent of first line
            if (resourceOptions != null) {
                top += resourceOptions.getTextStartPos(1);
            }

            // draw all lines of text
            int i = lines.size() - 1;
            String line = lines.get(i);

            // align horizontally
            float textWidth = paint.measureText(line);
            textWidth = textWidth > width ? width : textWidth;
            float left = 0;
            if (resourceOptions != null) {
                switch (resourceOptions.getHorizontalAlign()) {
                case CENTER:
                    left = (width - textWidth) / 2;
                    break;
                case RIGHT:
                    left = width - textWidth;
                default:
                    break;
                }
            }

            Log.w("text", "Lines size " + lines.size());
            Log.w("text", "Last line " + line);
            Log.w("text", "Text size " + textSize);
            Log.w("text", "Text width " + textWidth);
            Log.w("text", "Left top " + left + "  " + top);

            // return result
            CharPosition result = new CharPosition();
            result.setX(left + (i == 0 ? startIntent : 0) + textWidth);
            result.setY(top + (-paint.getFontMetrics().top + xreText.getLeading()) * i + textSize);
            return result;
        }
        return null;
    }

    /**
     * <p>
     * Asserts that the dimensions is not <code>null</code> and has exactly two elements.
     * </p>
     *
     * @throws IllegalStateException if dimensions is null or doesn't contain exactly 2 elements
     */
    private void assertDimensions() {
        if (dimensions == null || dimensions.length != 2) {
            throw new IllegalStateException("Property dimensions is null or doesn't have exactly two elements.");
        }
    }
    
    public List<XREView> getAllChildren() {
    	List<XREView> result = new ArrayList<XREView>();
    	getAllChildrenCore(this, result);
    	return result;
    }
    
    private static void getAllChildrenCore(XREView xreView, List<XREView> allChildren) {
    	if (xreView.children == null) {
    		return;
    	}
    	
    	for (XREView child : xreView.children) {
    		allChildren.add(child);
    		getAllChildrenCore(child, allChildren);
    	}
    }

    public boolean isLeaf() {
    	return (getId() != XREView.ID_ROOT_VIEW && mask == null && !clip && visible && !playingAnimation);
    }

	/**
	 * @param playingAnimation the playingAnimation to set
	 */
	public synchronized void setPlayingAnimation(boolean playingAnimation) {
		this.playingAnimation = playingAnimation;
	}

	/**
	 * @return the playingAnimation
	 */
	public synchronized boolean isPlayingAnimation() {
		return playingAnimation;
	}

	/**
	 * @param androidXREView the androidXREView to set
	 */
	public void setAndroidXREView(AndroidXREView androidXREView) {
		this.androidXREView = androidXREView;
	}

	/**
	 * @return the androidXREView
	 */
	public AndroidXREView getAndroidXREView() {
		return androidXREView;
	}

	/**
	 * @param paintPropertyDisabled the paintPropertyDisabled to set
	 */
	public void setPaintPropertyDisabled(boolean paintPropertyDisabled) {
		this.paintPropertyDisabled = paintPropertyDisabled;
	}

	/**
	 * @return the paintPropertyDisabled
	 */
	public boolean isPaintPropertyDisabled() {
		return paintPropertyDisabled;
	}

	/**
     * Utility class, that is simple JavaBean. It holds x and y coordinates for char position.
     * <p>
     * AndroidAssembly3 this class was added for Android Assembly 3
     *
     * @author TCSASSEMBLER
     * @version 1.0
     */
    public static class CharPosition {
        /**
         * Represents x coordinate of the char.
         * <p>
         * Can be any value. Has getter and setter.
         */
        private float x;

        /**
         * Represents y coordinate of the char.
         * <p>
         * Can be any value. Has getter and setter.
         */
        private float y;

        /**
         * Gets the x coordinate of the char.
         *
         * @return the x coordinate of the char
         */
        public float getX() {
            return x;
        }

        /**
         * Sets the x coordinate of the char.
         *
         * @param x the x coordinate of the char
         */
        public void setX(float x) {
            this.x = x;
        }

        /**
         * Gets the y coordinate of the char.
         *
         * @return the y coordinate of the char
         */
        public float getY() {
            return y;
        }

        /**
         * Sets the y coordinate of the char.
         *
         * @param y the y coordinate of the char
         */
        public void setY(float y) {
            this.y = y;
        }
    }
}
